#include "GlSpriteRenderer.h"

#include <D3DX11async.h>
#include <algorithm>
#include "GlCamera.h"
#include "GlConfigFile.h"
#include "GlDxGraphicComponent.h"
#include "GlDxSprite.h"
#include "GlDxSpotLight.h"
#include "GlMatrix4x4.h"
#include "GlPosition.h"
#include "GlSize.h"
#include "Utility.h"

namespace GraphicLibrary
{
	//---------------------------------------------------
	const DxSprite* SpriteRenderer::SpriteIterator::peek() const
	{
		return (*mIterator)->cast<DxSprite>();
	}

	//---------------------------------------------------
	void SpriteRenderer::SpriteIterator::next()
	{
		do
		{
			++mIterator;
		}
		while(hasMore() && !peek());
	}

	//---------------------------------------------------
	bool SpriteRenderer::SpriteIterator::hasMore() const
	{
		return mIterator != mEnd;
	}
}

namespace GraphicLibrary
{
	SpriteRenderer *SpriteRenderer::msSingleton(0);
	//---------------------------------------------------
	SpriteRenderer::~SpriteRenderer()
	{
		for(int i = 0; i != VbCount; ++i)
			if(mVsBuffers[i])
				mVsBuffers[i]->Release();

		for(int i = 0; i != RsCount; ++i)
			if(mPsBuffers[i])
				mPsBuffers[i]->Release();

		if(mSampler)
			mSampler->Release();
		if(mVs)
			mVs->Release();
		for(int i = 0; i != RsCount; ++i)
			if(mPs[i])
				mPs[i]->Release();
		if(mInputLayout)
			mInputLayout->Release();
	}

	//---------------------------------------------------
	void SpriteRenderer::tick(const float deltaTime)
	{
		for(DxGraphicComponentArray::iterator i = mSprites.begin(); i != mSprites.end(); ++i)
			(*i)->tick(deltaTime);
	}

	//---------------------------------------------------
	void SpriteRenderer::add(DxGraphicComponent *s)
	{
		DxGraphicComponentArray::iterator i = findLocation(s);
		if(i == mSprites.end() || *i != s)
			mSprites.insert(i, s);
	}

	//---------------------------------------------------
	void SpriteRenderer::remove(DxGraphicComponent *s)
	{
		DxGraphicComponentArray::iterator i = findLocation(s);
		if(i != mSprites.end() && (*i) == s)
			mSprites.erase(i);
	}

	//---------------------------------------------------
	void SpriteRenderer::renderAll(ID3D11DeviceContext *ic) const
	{
		setViewMatrix(ic, mVsBuffers[VbView]);
		for(DxGraphicComponentArray::const_iterator i = mSprites.begin(); i != mSprites.end(); ++i)
			_render(ic, *i);
	}

	//---------------------------------------------------
	SpriteRenderer::SpriteRenderer(ID3D11Device *device, const Size &screenSize)
		: mState(RsNone)
		, mInputLayout(0)
		, mVs(0)
		, mSampler(0)
		//, mRasterizer(0)
	{
		ZeroMemory(mPs, sizeof(ID3D11PixelShader*) * RsCount);
		ZeroMemory(mVsBuffers, sizeof(ID3D11Buffer*) * VbCount);
		ZeroMemory(mPsBuffers, sizeof(ID3D11Buffer*) * RsCount);
		ID3D10Blob* compiledProgram = compileShader(Utility::getResource("SimpleSpriteDx9.fx"), "PS_Sprite", "ps_4_0_level_9_3");
		HRESULT res = device->CreatePixelShader(compiledProgram->GetBufferPointer(), compiledProgram->GetBufferSize(), 0, &mPs[RsSprite]);
		compiledProgram->Release();
		if(res != S_OK)
			throw ElUnableToCompileShaderPrograms;

		compiledProgram = compileShader(Utility::getResource("SimpleSpriteDx9.fx"), "PS_SpotLight", "ps_4_0_level_9_3");
		res = device->CreatePixelShader(compiledProgram->GetBufferPointer(), compiledProgram->GetBufferSize(), 0, &mPs[RsSpotLight]);
		compiledProgram->Release();
		if(res != S_OK)
			throw ElUnableToCompileShaderPrograms;

		compiledProgram = compileShader(Utility::getResource("SimpleSpriteDx9.fx"), "VS", "vs_4_0_level_9_3");
		res = device->CreateVertexShader(compiledProgram->GetBufferPointer(), compiledProgram->GetBufferSize(), 0, &mVs);
		if(res != S_OK)
		{
			compiledProgram->Release();
			throw ElUnableToCompileShaderPrograms;
		}

		D3D11_INPUT_ELEMENT_DESC ilDesc[2];
		ilDesc[0].SemanticName = "POSITION";
		ilDesc[0].SemanticIndex = 0;
		ilDesc[0].Format = DXGI_FORMAT_R32G32_FLOAT;
		ilDesc[0].InputSlot = 0;
		ilDesc[0].AlignedByteOffset = 0;
		ilDesc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		ilDesc[0].InstanceDataStepRate = 0;

		ilDesc[1].SemanticName = "UV";
		ilDesc[1].SemanticIndex = 0;
		ilDesc[1].Format = DXGI_FORMAT_R32G32_FLOAT;
		ilDesc[1].InputSlot = 0;
		ilDesc[1].AlignedByteOffset = sizeof(float) * 2;
		ilDesc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		ilDesc[1].InstanceDataStepRate = 0;

		res = device->CreateInputLayout(ilDesc, 2, compiledProgram->GetBufferPointer(), compiledProgram->GetBufferSize(), &mInputLayout);
		compiledProgram->Release();
		if(res != S_OK)
			throw ElUnableToCreateInputLayout;

		mSampler = createSampler(device);

		mVsBuffers[VbProjection] = createProjectionBuffer(device, screenSize);
		mVsBuffers[VbWorld] = createMutableBuffer(device, sizeof(float) * 4 * 4);
		mVsBuffers[VbView] = createMutableBuffer(device, sizeof(float) * 4 * 4);

		mPsBuffers[RsSprite] = createMutableBuffer(device, sizeof(float) * 4 * 3);
		mPsBuffers[RsSpotLight] = createMutableBuffer(device, sizeof(float) * 4);

		//D3D11_RASTERIZER_DESC rDesc;
		//rDesc.FillMode = (D3D11_FILL_MODE)1;
		//rDesc.CullMode = D3D11_CULL_BACK;
		//rDesc.FrontCounterClockwise = false;
		//rDesc.DepthBias = 0;
		//rDesc.DepthBiasClamp = 0.0f;
		//rDesc.SlopeScaledDepthBias = 0.0f;
		//rDesc.DepthClipEnable = true;
		//rDesc.ScissorEnable = false;
		//rDesc.MultisampleEnable = false;
		//rDesc.AntialiasedLineEnable = false;
		//res = device->CreateRasterizerState(&rDesc, &mRasterizer);
	}

	//---------------------------------------------------
	SpriteRenderer::DxGraphicComponentArray::iterator SpriteRenderer::findLocation(const DxGraphicComponent *s) const
	{
		class Less
		{
			public:
				inline bool operator () (const DxGraphicComponent *left, const DxGraphicComponent *right) const
				{
					if(left->getLayer() > right->getLayer())
						return false;
					if(left->getLayer() < right->getLayer())
						return true;
					return left < right;
				}
		};

		DxGraphicComponentArray::iterator i = std::lower_bound(const_cast<SpriteRenderer*>(this)->mSprites.begin(), const_cast<SpriteRenderer*>(this)->mSprites.end(), s, Less());
		return i;
	}

	//---------------------------------------------------
	ID3D10Blob* SpriteRenderer::compileShader(const char *fileName, const char *entry, const char *profile) const
	{
		ID3D10Blob* compiledProgram = 0;
		ID3D10Blob* errors = 0;
		HRESULT res = D3DX11CompileFromFile(fileName, 0, 0, entry, profile, 0, 0, 0, &compiledProgram, &errors, 0);
		if(errors)
		{
			const char *message = reinterpret_cast<const char*>(errors->GetBufferPointer());
			errors->Release();
		}
		if(res != S_OK)
			throw ElUnableToCompileShaderPrograms;
		return compiledProgram;
	}

	//---------------------------------------------------
	void SpriteRenderer::setDc(ID3D11DeviceContext *ic, const RenderingState newState) const
	{
		if(mState == newState)
			return;

		mState = newState;
		ic->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		ic->IASetInputLayout(mInputLayout);
		ic->VSSetShader(mVs, 0, 0);
		ic->PSSetShader(mPs[mState], 0, 0);
		ic->VSSetConstantBuffers(0, VbCount, mVsBuffers);
		ic->PSSetConstantBuffers(0, 1, mPsBuffers + mState);
		//ic->RSSetState(mRasterizer);
		switch(mState)
		{
			case RsSprite :
				ic->PSSetSamplers(0, 1, &mSampler);
				break;
			case RsSpotLight :
				ic->PSSetSamplers(0, 0, 0);
				break;
		}
	}

	//---------------------------------------------------
	void SpriteRenderer::_render(ID3D11DeviceContext *ic, const DxGraphicComponent *gc) const
	{
		if(!gc->isVisible())
			return;
		const DxSprite *sprite = gc->cast<DxSprite>();
		if(sprite)
			render(ic, sprite);
		const DxSpotLight *spotLight = gc->cast<DxSpotLight>();
		if(spotLight)
			render(ic, spotLight);
	}

	//---------------------------------------------------
	ID3D11Buffer* SpriteRenderer::createProjectionBuffer(ID3D11Device *device, const Size &screenSize) const
	{
		Matrix4x4 mat;
		mat.setScale(1.0f / screenSize.mWidth, 1.0f / screenSize.mHeight, 1.0f);

		D3D11_BUFFER_DESC bDesc;
		bDesc.ByteWidth = sizeof(float) * 4 * 4;
		bDesc.Usage = D3D11_USAGE_IMMUTABLE;
		bDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		bDesc.CPUAccessFlags = 0;
		bDesc.MiscFlags = 0;
		bDesc.StructureByteStride = sizeof(float) * 4 * 4;
		D3D11_SUBRESOURCE_DATA dDesc;
		dDesc.pSysMem = mat.asFloatPointer();
		dDesc.SysMemPitch = sizeof(float) * 4 * 4;
		ID3D11Buffer *buffer = 0;
		HRESULT res = device->CreateBuffer(&bDesc, &dDesc, &buffer);
		if(res != S_OK)
			throw ElUnableToCreateInputLayout;
		return buffer;
	}

	//---------------------------------------------------
	ID3D11Buffer* SpriteRenderer::createMutableBuffer(ID3D11Device *device, const UINT size) const
	{
		D3D11_BUFFER_DESC bDesc;
		bDesc.ByteWidth = size;
		bDesc.Usage = D3D11_USAGE_DYNAMIC;
		bDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		bDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		bDesc.MiscFlags = 0;
		bDesc.StructureByteStride = size;
		ID3D11Buffer *buffer = 0;
		HRESULT res = device->CreateBuffer(&bDesc, 0, &buffer);
		if(res != S_OK)
			throw ElUnableToCreateInputLayout;
		return buffer;
	}

	//---------------------------------------------------
	void SpriteRenderer::setBuffer(ID3D11DeviceContext *ic, ID3D11Buffer *buffer, const float *data) const
	{
		D3D11_MAPPED_SUBRESOURCE resource;
		HRESULT res = ic->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
		D3D11_BUFFER_DESC desc;
		buffer->GetDesc(&desc);
		if(res != S_OK)
			throw ElUnableToUpdateBufferData;
		memcpy(resource.pData, data, desc.ByteWidth);
		ic->Unmap(buffer, 0);
	}

	//---------------------------------------------------
	void SpriteRenderer::setViewMatrix(ID3D11DeviceContext *ic, ID3D11Buffer *buffer) const
	{
		setBuffer(ic, buffer, Camera::GetSingleton()->getTransformationMatrix().asFloatPointer());
	}

	//---------------------------------------------------
	void SpriteRenderer::setWorldMatrix(ID3D11DeviceContext *ic, ID3D11Buffer *buffer, const DxGraphicComponent *s) const
	{
		setBuffer(ic, buffer, s->getTransformationMatrix().asFloatPointer());
	}

	//---------------------------------------------------
	void SpriteRenderer::setShadingBuffer(ID3D11DeviceContext *ic, ID3D11Buffer *buffer, const DxSprite *s) const
	{
		float data[4 * 3];
		data[0] = s->getUvShift().mX;
		data[1] = s->getUvShift().mY;
		data[2] = s->getSizeMul().mWidth;
		data[3] = s->getSizeMul().mHeight;
		Size frameSize = s->getTextureRelativeFrameSize();
		Size texelSize = s->getTexelSize();
		data[4] = frameSize.mWidth;
		data[5] = frameSize.mHeight;
		data[6] = texelSize.mWidth;
		data[7] = texelSize.mHeight;
		data[8] = GraphicComponent::msSaturation;
		data[9] = 1.0f - s->getTransparency();
		setBuffer(ic, buffer, data);
	}

	//---------------------------------------------------
	void SpriteRenderer::setLightingBuffer(ID3D11DeviceContext *ic, ID3D11Buffer *buffer, const DxSpotLight *sl) const
	{
		float data[8];
		data[0] = sl->getColor()[0];
		data[1] = sl->getColor()[1];
		data[2] = sl->getColor()[2];
		data[3] = sl->getColor()[3];
		float avg = (data[0] + data[1] + data[2]) / 3.0f;
		data[0] = Utility::lerp(data[0], avg, GraphicComponent::msSaturation);
		data[1] = Utility::lerp(data[1], avg, GraphicComponent::msSaturation);
		data[2] = Utility::lerp(data[2], avg, GraphicComponent::msSaturation);
		setBuffer(ic, buffer, data);
	}

	//---------------------------------------------------
	ID3D11SamplerState* SpriteRenderer::createSampler(ID3D11Device *device) const
	{
		ID3D11SamplerState *sampler = 0;
		D3D11_SAMPLER_DESC desc;
		desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
		desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
		desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
		desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
		desc.MipLODBias = 0.0f;
		desc.MaxAnisotropy = 1;
		desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
		desc.BorderColor[0] = 1.0f;
		desc.BorderColor[1] = 1.0f;
		desc.BorderColor[2] = 1.0f;
		desc.BorderColor[3] = 1.0f;
		desc.MinLOD = 0;
		desc.MaxLOD = FLT_MAX;
		HRESULT res = device->CreateSamplerState(&desc, &sampler);
		if(res != S_OK)
			throw ElUnableToCreateSamplerState;
		return sampler;
	}

	//---------------------------------------------------
	void SpriteRenderer::render(ID3D11DeviceContext *ic, const DxSprite *sprite) const
	{
		setDc(ic, RsSprite);

		setWorldMatrix(ic, mVsBuffers[VbWorld], sprite);
		setShadingBuffer(ic, mPsBuffers[RsSprite], sprite);
		sprite->apply(ic);
		ic->DrawIndexed(6, 0, 0);
	}

	//---------------------------------------------------
	void SpriteRenderer::render(ID3D11DeviceContext *ic, const DxSpotLight *spotLight) const
	{
		setDc(ic, RsSpotLight);

		setWorldMatrix(ic, mVsBuffers[VbWorld], spotLight);
		setLightingBuffer(ic, mPsBuffers[RsSpotLight], spotLight);
		spotLight->apply(ic);
		ic->DrawIndexed(spotLight->getIndexCount(), 0, 0);
	}
}
